package pers.karl.mongodb.common.dao.impl;

import com.mongodb.client.result.UpdateResult;
import org.springframework.data.domain.Example;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.mongodb.repository.support.SimpleMongoRepository;
import org.springframework.util.Assert;
import pers.karl.mongodb.common.dao.BaseMongoRepository;
import pers.karl.mongodb.common.util.ReflectUtils;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.List;

/**
 * @author karl
 * @description: MongoDb 通用DAO实现类
 * @create 2019-06-09 09:10
 */
public class BaseMongoRepositoryImpl<T,ID extends Serializable> extends SimpleMongoRepository<T, ID> implements BaseMongoRepository<T,ID> {

    protected final MongoOperations mongoTemplate;

    protected final MongoEntityInformation<T, ID> entityInformation;

    private Class<T> entityClass;
    /** 属性列表 */
    private List<Field> fieldList;

    private String idAttribute;

    public BaseMongoRepositoryImpl(MongoEntityInformation<T, ID> metadata, MongoOperations mongoOperations) {
        super(metadata, mongoOperations);
        this.mongoTemplate = mongoOperations;
        this.entityInformation = metadata;
        idAttribute = entityInformation.getIdAttribute();
        entityClass = entityInformation.getJavaType();
        fieldList = ReflectUtils.listField(entityClass);
    }

    @Override
    public long updateNotNull(T entity) throws Exception {
        Query query = new Query(Criteria.where(idAttribute).is(entityInformation.getRequiredId(entity)));
        Update update= getUpdate(entity);

        UpdateResult result = mongoTemplate.updateFirst(query,update,entityClass);
        if (result != null) {
            return result.getMatchedCount();
        }

        return 0;
    }

    private Query getQuery(T entity) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        Criteria criteria = null;

        for (Field field : fieldList) {
            String fieldName = field.getName();
            Object beanValue = ReflectUtils.getBeanValue(entityClass, entity, fieldName);
            if (beanValue != null){
                String keyName = getKeyName(field,fieldName);

                if (criteria == null) {
                    criteria = Criteria.where(keyName).is(beanValue);
                } else {
                    criteria.and(keyName).is(beanValue);
                }
            }
        }

        if(criteria == null){
            criteria = new Criteria();
        }

        return new Query(criteria);
    }

    private Update getUpdate(T entity) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        Update update = new Update();

        for(Field field : fieldList){
            String fieldName = field.getName();
            Object beanValue = ReflectUtils.getBeanValue(entityClass, entity, fieldName);
            if(beanValue != null){
                String keyName = getKeyName(field,fieldName);
                update.set(keyName, beanValue);
            }
        }

        return update;
    }

    private String getKeyName(Field field, String fieldName){
        String keyName = fieldName;
        org.springframework.data.mongodb.core.mapping.Field fieldAnnotation = field.getAnnotation(org.springframework.data.mongodb.core.mapping.Field.class);
        if (fieldAnnotation != null) {
            keyName = fieldAnnotation.value();
        }
        return keyName;
    }

    @Override
    public T getById(ID id) {
        Assert.notNull(id, "The given id must not be null!");
        return mongoTemplate.findById(id,entityClass);
    }

    @Override
    public T getOne(T entity) throws Exception {
        Query query = getQuery(entity);
        List<T> list = mongoTemplate.find(query.limit(2), entityClass);
        int listSize = list.size();
        if(listSize == 0){
            return null;
        }

        if (listSize > 1) {
            throw new IllegalStateException("the query result not one record");
        }

        return list.get(0);
    }

    @Override
    public List<T> list(T entity) throws Exception {
        Query query = getQuery(entity);
        return mongoTemplate.find(query , entityClass);
    }

    @Override
    public long count(T entity) {
        Assert.notNull(entity,"entity must not be null");
        return count(Example.of(entity));
    }

}
